package panda.natalia.gothread.suspend;

import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodInsnNode;
import panda.natalia.gothread.suspend.instrument.DontInstrument;

import javax.sound.midi.Instrument;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * a simple framework of java fiber
 *
 * @Author: scpanda2007
 * @Date: 2019/11/28 15:32
 *
 *  固定常量定义
 */
public class GlobalDefine {

    public static final String STRAND_NAME = /* Strand.class.getName() */ "panda/natalia/gothread/strands/Strand";
    public static final String GOTHREAD_CLASS_NAME = "panda/natalia/gothread/strands/suspend/GoThread";
    public static final String SUSPEND_EXECUTION_NAME = "panda/natalia/gothread/strands/suspend/SuspendException";

    public static final String LAMDA_METHOD_PREFIX = "lambda$";

    public static boolean isAllowedToBlock(String className, String methodName) { return STRAND_NAME.equals(className);}

    private static final BlockingMethod BLOCKING_METHODS[] = {
        new BlockingMethod("java/lang/Thread", "sleep", "(J)V", "(JI)V"),
        new BlockingMethod("java/lang/Thread", "join", "()V", "(J)V", "(JI)V"),
        new BlockingMethod("java/lang/Object", "wait", "()V", "(J)V", "(JI)V"),
    };

    public static final String SUSPENDABLE_DESC = Type.getDescriptor(Suspendable.class);
    public static final String INSTRUMENTED_DESC = Type.getDescriptor(Instrumented.class);
    public static final String DONT_INSTRUMENT_DESC = Type.getDescriptor(DontInstrument.class);

    private static class BlockingMethod {
        private final String owner;
        private final String name;
        private final String[] descs;

        private BlockingMethod(String owner, String name, String ...descs){
            this.owner = owner;
            this.name = name;
            this.descs = descs;
        }

        boolean match(MethodInsnNode min){
            if(owner.equals(min.owner) && name.equals(min.name)){
                for(String desc : descs){
                    if(desc.equals(min.desc)) return true;
                }
            }
            return false;
        }

    }

    private static final Set<String> yieldMethods = new HashSet<>(Arrays.asList(new String[]{
        "park", "yield", "parkAndUnpark", "yieldAndUnpark", "parkAndSerialize", "parkAndCustomSerialize"
    }));

    public static final String STACK_NAME = "panda/natalia/gothread/GoThreadVarStack";

    public static boolean isYieldMethod(String className, String methodName){
        return GOTHREAD_CLASS_NAME.equals(className) && yieldMethods.contains(methodName);
    }

    public static int blockingCallIdx(MethodInsnNode ins){
        for (int i = 0, n = BLOCKING_METHODS.length; i < n; i++) {
            if(BLOCKING_METHODS[i].match(ins)) return i;
        }
        return -1;
    }

    public static int[] toIntArray(List<Integer> suspOffsetsAfterInstrL){
        if(suspOffsetsAfterInstrL == null) return null;
        return suspOffsetsAfterInstrL.stream().sequential().filter(integer -> integer != null).mapToInt(Integer::intValue).toArray();
    }
}
